<!--
 * @Description: 
 * @Author: 胡涛
 * @Date: 2023-09-20 14:32:26
 * @LastEditors: 胡涛
 * @LastEditTime: 2023-12-17 11:46:57
-->
<template>
  <div class="zh-monaco-editor" :style="editorStyle">
    <div class="zh-monaco-editor-toolbar">
      <div class="title">代码编辑器</div>
      <div class="actions">
        <FullscreenExitOutlined v-if="isFullscreen" @click="switchFullScreen" />
        <FullscreenOutlined v-else @click="switchFullScreen" />
      </div>
    </div>
    <div ref="containerRef" class="zh-monaco-editor-container"></div>
  </div>
</template>

<script lang="ts" setup>
import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js";
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution";
import { ref, watch, computed, onMounted, onDeactivated, toRaw } from "vue";
import {
  FullscreenOutlined,
  FullscreenExitOutlined,
} from "@ant-design/icons-vue";
type LanguageType =
  | "json"
  | "xml"
  | "html"
  | "razor"
  | "pug"
  | "tlc"
  | "yaml"
  | "ini"
  | "bat"
  | "shell"
  | "powershell"
  | "markdown"
  | "dockerfile"
  | "scheme"
  | "csharp"
  | "vb"
  | "cpp"
  | "csp"
  | "javascript"
  | "ruby"
  | "java"
  | "python"
  | "fsharp"
  | "swift"
  | "objective-c"
  | "go"
  | "kontlin"
  | "php"
  | "perl"
  | "lua"
  | "sql"
  | "plsql"
  | "mysql"
  | "css"
  | "less"
  | "scss";

type MonacoEditorProps = {
  value: string;
  language: LanguageType;
  options?: any;
  extraLibSource?: string;
  extraLibName?: string;
  checkJs?: boolean;
  height: number | string;
};

const props = withDefaults(defineProps<MonacoEditorProps>(), {
  value: "",
  extraLibSource: "",
  checkJs: true,
  height: 300,
  language: "json",
  options: () => {
    return {};
  },
});

const emits = defineEmits(["update:value", "change"]);
const containerRef = ref<any>();
const defaultOpts: any = {
  theme: "vs-dark", // 编辑器主题：vs, hc-black, or vs-dark，更多选择详见官网
  roundedSelection: false, // 右侧不显示编辑器预览框
  autoIndent: true, // 自动缩进
  features: ["coreCommands", "find"],
  automaticLayout: false, //自动布局
  readOnly: false, // 是否可编辑
  foldingStrategy: "indentation", //代码可分段折叠
};
const isFullscreen = ref(false);
const monacoEditor = ref<monaco.editor.IStandaloneCodeEditor>();
const emiting = ref(false);

const editorStyle: any = computed(() => {
  if (isFullscreen.value) {
    return {
      position: "fixed",
      zIndex: 99999,
      left: 0,
      top: 0,
      right: 0,
      bottom: 0,
    };
  } else {
    if (props.height && typeof props.height == "string") {
      return { height: props.height };
    } else if (props.height) {
      return { height: props.height + "px" };
    } else {
      return {};
    }
  }
});

watch(
  () => props.value,
  (val) => {
    if (emiting.value) {
      return;
    }
    val && setValue(val);
  }
);

/**
 * 切换全屏展示
 */
const switchFullScreen = () => {
  isFullscreen.value = !isFullscreen.value;
  setTimeout(() => {
    if (monacoEditor.value) {
      toRaw(monacoEditor.value).layout();
    }
  }, 0);
};

/**
 * 页面事件处理
 */
const onWindowResizeHandler = () => {
  if (monacoEditor.value) {
    toRaw(monacoEditor.value).layout();
  }
};

const timeout = ref();

/**
 * 初始化编辑器
 */
const initEditor = () => {
  if (props.extraLibSource) {
    monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
      noSemanticValidation: !props.checkJs,
      noSyntaxValidation: !props.checkJs,
    });
    monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
      target: monaco.languages.typescript.ScriptTarget.ES2015,
      allowNonTsExtensions: true,
      checkJs: props.checkJs,
      allowJs: props.checkJs,
    });

    // 扩展模型数据加载
    var libUri = `ts:filename/${props.extraLibName || "app"}.d.ts`;
    monaco.languages.typescript.javascriptDefaults.addExtraLib(
      props.extraLibSource,
      libUri
    );
    let model = monaco.editor.getModel(monaco.Uri.parse(libUri));
    if (model) {
      model.dispose();
    }
    monaco.editor.createModel(
      props.extraLibSource,
      "typescript",
      monaco.Uri.parse(libUri)
    );
  }
  // 初始化container的内容，销毁之前生成的编辑器
  let container = containerRef.value as HTMLElement;
  if (monacoEditor.value) {
    toRaw(monacoEditor.value).dispose();
    container.innerHTML = "";
  }
  let value = props.value || props.options.value;
  let opts: any = Object.assign(defaultOpts, props.options, {
    language: props.language,
    value,
  });
  // 生成编辑器对象
  let editor = monaco.editor.create(container, opts);

  /**
   * 处理内容变化事件
   */
  editor.onDidBlurEditorText(() => {
    emiting.value = true;
    let val = toRaw(editor).getValue();
    emits("change", val);
    emits("update:value", val);
    timeout.value && clearTimeout(timeout.value);
    timeout.value = setTimeout(() => {
      emiting.value = false;
    }, 100);
  });
  monacoEditor.value = editor;
};

/**
 * 手动给组件赋值
 */
const setValue = (val: string) => {
  if (monacoEditor.value) {
    return toRaw(monacoEditor.value).setValue(val);
  }
};

/**
 * 供父组件调用手动获取值
 */
const getValue = () => {
  if (monacoEditor.value) {
    return toRaw(monacoEditor.value).getValue();
  } else {
    return null;
  }
};

onMounted(() => {
  window.addEventListener("resize", onWindowResizeHandler, false);
  setTimeout(() => initEditor(), 200);
});

onDeactivated(() => {
  window.removeEventListener("resize", onWindowResizeHandler, false);
  if (monacoEditor.value) {
    toRaw(monacoEditor.value).dispose();
  }
});
</script>

<style lang="less">
.zh-monaco-editor {
  display: flex;
  flex-direction: column;
  background: #1e1e1e;
  overflow: hidden;

  &-toolbar {
    display: flex;
    padding: 5px 10px;
    color: #fff;
    border-bottom: 1px solid #666;
    line-height: 18px;

    .title {
      flex: 1;
    }
  }

  &-container {
    flex: 1;
    overflow: hidden;
  }
}
</style>
